/***
 * * ASF - ACE Simulation Framework
 * * Copyright (C) 2007 Politecnico di Milano
 * * <mailto: daniel@dubois.it> <mailto: dinitto@elet.polimi.it>
 * *
 * * This library is free software; you can redistribute it and/or modify it
 * * under the terms of the GNU Lesser General Public License as published by
 * * the Free Software Foundation; either version 2.1 of the License, or (at
 * * your option) any later version.
 * *
 * * This library is distributed in the hope that it will be useful, but
 * * WITHOUT ANY WARRANTY; without even the implied warranty of
 * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * * General Public License for more details.
 * *
 * * You should have received a copy of the GNU Lesser General Public License
 * * along with this library; if not, write to the Free Software Foundation,
 * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 ***/

package polimi.reds.optimizer;

import java.io.Serializable;

import org.apache.commons.cli.*;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

@SuppressWarnings("static-access")
/**
 * Class SimulationOptions
 * 
 * This class represents the options of the current simulation.
 * 
 * @author Daniele Joseph Dubois
 */
public class OptimizerStrategyOptions implements Cloneable {


	/** Internal list of options */
	private Map<String,MyOption> optionsMap = new HashMap<String,MyOption>();

	/**
	 * Adds a new option
	 * 
	 * @param name
	 *            Name of the new option
	 * @param description
	 *            Description for the new option
	 * @param value
	 *            Default value for this option
	 */
	public void addOption(String name, String description, Object value) {
		optionsMap.put(name,new MyOption(name, description, value));
	}

	/**
	 * Gets the value for the specified option as object
	 * 
	 * @param name
	 *            Name of the option
	 * @return Value for the specified option
	 */
	public Object getOption(String name) {
		
		MyOption o = optionsMap.get(name);
		
		if (o == null) {
			Logger.getLogger("polimi.reds.optimizer.OptimizerOptions").severe("unknown option name: " + name);
			System.exit(0);
		}
		
		return o.getValue();
	}
	
	/**
	 * Gets the value for the specified option as integer
	 * 
	 * @param name
	 *            Name of the option
	 * @return Value for the specified option
	 */
	public Integer getIntegerOption(String name) {
		return (Integer) getOption(name);
	}
	
	/**
	 * Gets the value for the specified option as boolean
	 * 
	 * @param name
	 *            Name of the option
	 * @return Value for the specified option
	 */
	public Boolean getBooleanOption(String name) {
		return (Boolean) getOption(name);
	}
	
	/**
	 * Creates a name/value map for all the options
	 *       
	 * @return Name/vale couple of values for all the options
	 */
	public Map<String,Object> createOptionsMap() {
		Map<String,Object> m = new HashMap<String,Object>();
		
		for (String key : optionsMap.keySet()) {
			m.put(key, optionsMap.get(key).getValue());
		}
		
		return m;
	}
	
	public OptimizerStrategyOptions clone() {
		OptimizerStrategyOptions newOptions = new OptimizerStrategyOptions();
		newOptions.optionsMap = new HashMap<String,MyOption>(optionsMap);
		return newOptions;
	}

	/**
	 * Parse the command line arguments in order to extract all the options.
	 * 
	 * @param args
	 *            Command line arguments
	 * 
	 * @throws Exception
	 *             If an invalid option has been provided
	 */
	public void parse(String[] args) throws Exception {
		Options options = new Options();
		options.addOption(OptionBuilder.withDescription(
				"print usage information").create("help"));

		for (String key : optionsMap.keySet()) {

			MyOption o = optionsMap.get(key);
			
			String argument;
			if (o.getValue() instanceof Boolean) {
				argument = "true/false";
			} else if (o.getValue() instanceof Integer) {
				argument = "number";
			} else {
				argument = "...";
			}

			options.addOption(OptionBuilder.withArgName(argument)
					.withDescription(
							o.getDescription() + " (default: " + o.getValue()
									+ ")").hasArgs().create(o.getName()));
		}

		CommandLineParser parser = new GnuParser();
		CommandLine line;

		try {
			line = parser.parse(options, args);
		} catch (ParseException e) {
			throw new Exception(e.getMessage());
		}

		if (line.hasOption("help")) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("cbr-simul [options]", options);
			System.exit(0);
		}

		for (String key : optionsMap.keySet()) {
			
			MyOption o = optionsMap.get(key);
			
			if (line.hasOption(o.getName())) {
				if (o.getValue() instanceof Boolean) {
					if (line.getOptionValue(o.getName()).equals("true")
							|| line.getOptionValue(o.getName()).equals("false")) {
						o
								.setValue(new Boolean(line.getOptionValue(o
										.getName())));
					} else {
						throw new Exception(o.getName()
								+ " must be true or false");
					}
				} else if (o.getValue() instanceof Integer) {
					Integer value;
					try {
						value = Integer.parseInt(line.getOptionValue(o
								.getName()));
						if ((value < 0) || (value > Integer.MAX_VALUE)) {
							throw new NumberFormatException();
						}
					} catch (NumberFormatException e) {
						throw new Exception(o.getName()
								+ " must be a number between 0 and " + Integer.MAX_VALUE);
					}
					o.setValue(new Integer(line.getOptionValue(o.getName())));
				} else if (o.getValue() instanceof String) {
					o.setValue(new String(line.getOptionValue(o.getName())));
				}
			}
		}
	}

	/**
	 * This inner class represents a single option.
	 * 
	 * @author Daniele Joseph Dubois
	 */
	private class MyOption implements Serializable {

		public final static long serialVersionUID = 7845666987L;

		/** Name of this option */
		private String name;

		/** Description of this option */
		private String description;

		/** Value of this option */
		private Object value;

		/**
		 * This constructor creates a new option using the given name,
		 * description and default value.
		 * 
		 * @param name
		 *            Name of the option
		 * @param description
		 *            Description of the option
		 * @param value
		 *            Default value for the option
		 */
		public MyOption(String name, String description, Object value) {
			this.name = name;
			this.description = description;
			this.value = value;
		}

		/**
		 * Get the name of this option
		 * 
		 * @return The name of this option
		 */
		public String getName() {
			return name;
		}

		/**
		 * Get the description of this option
		 * 
		 * @return The description of this option
		 */
		public String getDescription() {
			return description;
		}

		/**
		 * Gets the value of this option
		 * 
		 * @return The value of this option
		 */
		public Object getValue() {
			return value;
		}

		/**
		 * Sets a new value for this option
		 * 
		 * @param value
		 *            New value for this option to be set
		 */
		public void setValue(Object value) {
			this.value = value;
		}
	}
}
